package logic

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"sync"

	"mall/common/enumx"
	"mall/common/errorx"
	"mall/common/globalkey"
	"mall/common/modelx"
	"mall/service/attachment/rpc/attachmentclient"
	"mall/service/backend/common/utils/pointy"
	"mall/service/blog/model"
	"mall/service/blog/rpc/internal/svc"
	"mall/service/blog/rpc/types/blog"

	"github.com/elastic/go-elasticsearch/v7/esapi"
	"github.com/sirupsen/logrus"
	"github.com/volatiletech/null/v8"
	"github.com/zeromicro/go-zero/core/logx"
)

type DiagnoseLogic struct {
	ctx    context.Context
	svcCtx *svc.ServiceContext
	logx.Logger
}

func NewDiagnoseLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DiagnoseLogic {
	return &DiagnoseLogic{
		ctx:    ctx,
		svcCtx: svcCtx,
		Logger: logx.WithContext(ctx),
	}
}

func (l *DiagnoseLogic) Diagnose(in *blog.DiagnoseReq) (*blog.DiagnoseResp, error) {
	var count int
	var err error
	switch in.Diagnose {
	case 1: // 分析图片是否为残留记录（有删除，要重复请求一次）
		count, err = l.diagnoseAttachment(in) // BlogAttachmentRelationship ObjectID 无效则删除
	case 2: // 分析图片有效性
		count, err = l.diagnoseAttachmentUrl(in) // BlogAttachmentRelationship AttachmentSource、AttachmentImageURL 无效则日志显示
	case 3: // 分析 blog_knowledge_base_detail
		count, err = l.diagnoseDetail(in) // 日志显示重复的详情，删除无效的详情
	case 4: // 分析 blog_knowledge_base_meta
		count, err = l.diagnoseMetum(in) // BlogKnowledgeBaseMetum，日志显示并删除无效的meta
	case 5: // 分析 blog_term_relationships
		count, err = l.diagnoseTerm(in) // BlogTermRelationship，日志显示并删除无效的ObjectID
	case 6: // 分析 blog_knowledge_base_relationships
		count, err = l.diagnoseRelationship(in) // BlogKnowledgeBaseRelationship，日志显示并删除无效的EntryRelationshipID
	case 7: // 分析 blog_knowledge_base_meta  _thumbnail_id  _thumbnail_url
		count, err = l.diagnoseThumbnail(in) // BlogKnowledgeBaseMetum，_thumbnail_id 同步 _thumbnail_url 为 BlogAttachmentRelationship
	case 8: // 分析 blog_term_relationships
		count, err = l.diagnoseEntryByTerm(in) // 将品牌的title,slug写入BlogKnowledgeBaseMetum 12153 12154
	case 9: // 同步到 elasticsearch
		count, err = l.diagnoseEntryToEs(in)
	case 10: // 分析 blog_term_relationships
		count, err = l.diagnoseMetumByTerm(in) // 修正 _perfume_count
	case 11: // 分析 elasticsearch Aggs
		count, err = l.diagnoseMetumByAggs(in) // 修正 _perfume_count
	case 21: // 修复缩略图
		count, err = l.diagnoseEntryAttachment(in) // 修正 _perfume_count
	}

	if err != nil {
		return nil, err
	}

	return &blog.DiagnoseResp{
		Total: int64(count),
	}, nil
}

func (l *DiagnoseLogic) diagnoseAttachment(in *blog.DiagnoseReq) (count int, err error) { /////
	list, _, err := l.svcCtx.BlogPost.GetMedia(l.ctx, model.M{
		"page":     in.Current,
		"pageSize": in.PageSize,
		"orderBy":  model.BlogAttachmentRelationshipColumns.AttachmentRelationshipID,
	}, -1, nil)
	if err != nil {
		return 0, err
	}

	if len(list) > 0 {
		var postIds []any   // []model.BlogPost.ID
		var pageIds []any   // []model.BlogPage.ID
		var entryIds []any  // []model.BlogKnowledgeBasis.ID
		var termIds []any   // []model.BlogTerm.TermID
		var objIds []uint64 // []model.BlogAttachmentRelationship.ObjectID
		idMap := make(map[uint64]uint64)
		for _, item := range list {
			if _, ok := idMap[item.ObjectID]; !ok {
				idMap[item.ObjectID] = item.AttachmentRelationshipID

				switch enumx.PostType(item.ObjectType) {
				case enumx.PostTypePost:
					postIds = append(postIds, item.ObjectID)
				case enumx.PostTypePage:
					pageIds = append(pageIds, item.ObjectID)
				case enumx.PostTypeKnowledgeBase:
					entryIds = append(entryIds, item.ObjectID)
				default:
					if item.ObjectType > uint16(enumx.PostTypeTermOffset) {
						termIds = append(termIds, item.ObjectID)
					}
				}
			}
		}

		if len(postIds) > 0 {
			filterMap := make(map[string]map[string][]any)
			filterMap[model.BlogPostColumns.ID] = map[string][]any{
				"in": postIds,
			}
			postList, _, err := l.svcCtx.BlogPost.GetList(l.ctx, model.M{
				"select": ",",
			}, -1, filterMap)
			if err != nil {
				return 0, err
			}

			for _, post := range postList {
				if _, ok := idMap[post.ID]; ok {
					delete(idMap, post.ID)
				}
			}
		}

		if len(pageIds) > 0 {
			filterMap := make(map[string]map[string][]any)
			filterMap[model.BlogPageColumns.ID] = map[string][]any{
				"in": pageIds,
			}
			pageList, _, err := l.svcCtx.BlogPage.GetList(l.ctx, model.M{
				"select": ",",
			}, -1, filterMap)
			if err != nil {
				return 0, err
			}

			for _, page := range pageList {
				if _, ok := idMap[page.ID]; ok {
					delete(idMap, page.ID)
				}
			}
		}

		if len(entryIds) > 0 {
			filterMap := make(map[string]map[string][]any)
			filterMap[model.BlogKnowledgeBasisColumns.ID] = map[string][]any{
				"in": entryIds,
			}
			entryList, _, err := l.svcCtx.BlogBook.GetList(l.ctx, model.M{
				"select": ",",
			}, -1, filterMap)
			if err != nil {
				return 0, err
			}

			for _, entry := range entryList {
				if _, ok := idMap[entry.ID]; ok {
					delete(idMap, entry.ID)
				}
			}
		}

		if len(termIds) > 0 {
			filterMap := make(map[string]map[string][]any)
			filterMap[model.BlogTermColumns.TermID] = map[string][]any{
				"in": termIds,
			}
			termList, _, err := l.svcCtx.BlogTerm.GetList(l.ctx, model.M{
				"select": ",",
			}, -1, filterMap)
			if err != nil {
				return 0, err
			}

			for _, term := range termList {
				if _, ok := idMap[term.TermID]; ok {
					delete(idMap, term.TermID)
				}
			}
		}

		for objId, _ := range idMap {
			objIds = append(objIds, objId)
		}
		if len(objIds) > 0 {
			logrus.Info(fmt.Sprintf("BlogAttachmentRelationship ObjectID is missing: %+v", objIds))

			l.svcCtx.BlogTerm.DiagnoseMedia(l.ctx, objIds)
		}

		return len(objIds), nil
	}

	return
}

func (l *DiagnoseLogic) diagnoseAttachmentUrl(in *blog.DiagnoseReq) (count int, err error) { /////
	list, _, err := l.svcCtx.BlogPost.GetMedia(l.ctx, model.M{
		"page":     in.Current,
		"pageSize": in.PageSize,
		"orderBy":  model.BlogAttachmentRelationshipColumns.AttachmentRelationshipID,
	}, -1, nil)
	if err != nil {
		return 0, err
	}

	if len(list) > 0 {
		var objIds []uint64 // []model.BlogAttachmentRelationship.ObjectID
		idMap := make(map[uint64]uint16)
		for _, item := range list {
			if _, ok := idMap[item.ObjectID]; ok {
				continue
			}

			if strings.HasPrefix(item.AttachmentSource, "https://www.scentrobe.com/proxy") {
				//
			} else if strings.HasPrefix(item.AttachmentImageURL, "https://www.scentrobe.com/proxy") {
				//
			} else {
				continue
			}

			idMap[item.ObjectID] = item.ObjectType
			objIds = append(objIds, item.ObjectID)
		}

		if len(objIds) > 0 {
			logrus.Info(fmt.Sprintf("BlogAttachmentRelationship AttachmentSource or AttachmentImageURL is deprecated, ObjectID : %+v", objIds))
		}

		return len(objIds), nil
	}

	return
}

func (l *DiagnoseLogic) diagnoseDetail(in *blog.DiagnoseReq) (count int, err error) { /////
	list, _, err := l.svcCtx.BlogBook.GetDetail(l.ctx, model.M{
		"page":     in.Current,
		"pageSize": in.PageSize,
		"select":   ",",
		"orderBy":  model.BlogKnowledgeBaseDetailColumns.DetailID,
	}, -1, nil)
	if err != nil {
		return 0, err
	}

	if len(list) > 0 {
		var entryIds []any // []model.BlogKnowledgeBasis.ID
		idMap := make(map[uint64]uint64)
		var objIds []uint64 // []model.BlogKnowledgeBasis.ID
		var ids []uint64    // []model.BlogKnowledgeBaseDetail.DetailID
		for _, item := range list {
			if _, ok := idMap[item.KnowledgeBaseID]; !ok {
				idMap[item.KnowledgeBaseID] = item.DetailID

				entryIds = append(entryIds, item.KnowledgeBaseID)
			} else {
				logrus.Info(fmt.Sprintf("BlogKnowledgeBaseDetail KnowledgeBaseID is duplicated: %d", item.KnowledgeBaseID))
			}
		}

		if len(entryIds) > 0 {
			filterMap := make(map[string]map[string][]any)
			filterMap[model.BlogKnowledgeBasisColumns.ID] = map[string][]any{
				"in": entryIds,
			}
			entryList, _, err := l.svcCtx.BlogBook.GetList(l.ctx, model.M{
				"select": ",",
			}, -1, filterMap)
			if err != nil {
				return 0, err
			}

			for _, entry := range entryList {
				if _, ok := idMap[entry.ID]; ok {
					delete(idMap, entry.ID)
				}
			}
		}

		for objId, id := range idMap {
			objIds = append(objIds, objId)
			ids = append(ids, id)
		}
		if len(objIds) > 0 {
			logrus.Info(fmt.Sprintf("BlogKnowledgeBaseDetail KnowledgeBaseID is missing: %+v, %+v", objIds, ids))

			l.svcCtx.BlogTerm.DiagnoseDetail(l.ctx, ids)
		}

		return len(objIds), nil
	}

	return
}

func (l *DiagnoseLogic) diagnoseMetum(in *blog.DiagnoseReq) (count int, err error) { /////
	list, _, err := l.svcCtx.BlogBook.GetMeta(l.ctx, model.M{
		"page":     in.Current,
		"pageSize": in.PageSize,
		"orderBy":  model.BlogKnowledgeBaseMetumColumns.MetaID,
	}, -1, nil)
	if err != nil {
		return 0, err
	}

	if len(list) > 0 {
		var entryIds []any // []model.BlogKnowledgeBasis.ID
		idMap := make(map[uint64]uint64)
		var objIds []uint64 // []model.BlogKnowledgeBasis.ID
		for _, item := range list {
			if _, ok := idMap[item.KnowledgeBaseID]; !ok {
				idMap[item.KnowledgeBaseID] = item.MetaID

				entryIds = append(entryIds, item.KnowledgeBaseID)
			}
		}

		if len(entryIds) > 0 {
			filterMap := make(map[string]map[string][]any)
			filterMap[model.BlogKnowledgeBasisColumns.ID] = map[string][]any{
				"in": entryIds,
			}
			entryList, _, err := l.svcCtx.BlogBook.GetList(l.ctx, model.M{
				"select": ",",
			}, -1, filterMap)
			if err != nil {
				return 0, err
			}

			for _, entry := range entryList {
				if _, ok := idMap[entry.ID]; ok {
					delete(idMap, entry.ID)
				}
			}
		}

		for objId, _ := range idMap {
			objIds = append(objIds, objId)
		}
		if len(objIds) > 0 {
			logrus.Info(fmt.Sprintf("BlogKnowledgeBaseMetum KnowledgeBaseID is missing: %+v", objIds))

			l.svcCtx.BlogTerm.DiagnoseMetum(l.ctx, objIds)
		}

		return len(objIds), nil
	}

	return
}

func (l *DiagnoseLogic) diagnoseTerm(in *blog.DiagnoseReq) (count int, err error) { /////
	list, _, err := l.svcCtx.BlogTerm.GetRelationshipList(l.ctx, model.M{
		"page":     in.Current,
		"pageSize": in.PageSize,
		"orderBy":  model.BlogTermRelationshipColumns.TermRelationshipID,
	}, -1, nil)
	if err != nil {
		return 0, err
	}

	if len(list) > 0 {
		var entryIds []any // []model.BlogKnowledgeBasis.ID
		idMap := make(map[uint64]uint64)
		var objIds []uint64 // []model.BlogKnowledgeBasis.ID
		for _, item := range list {
			if _, ok := idMap[item.ObjectID]; !ok {
				idMap[item.ObjectID] = item.TermRelationshipID

				entryIds = append(entryIds, item.ObjectID)
			}
		}

		if len(entryIds) > 0 {
			filterMap := make(map[string]map[string][]any)
			filterMap[model.BlogKnowledgeBasisColumns.ID] = map[string][]any{
				"in": entryIds,
			}
			entryList, _, err := l.svcCtx.BlogBook.GetList(l.ctx, model.M{
				"select": ",",
			}, -1, filterMap)
			if err != nil {
				return 0, err
			}

			for _, entry := range entryList {
				if _, ok := idMap[entry.ID]; ok {
					delete(idMap, entry.ID)
				}
			}
		}

		for objId, _ := range idMap {
			objIds = append(objIds, objId)
		}
		if len(objIds) > 0 {
			logrus.Info(fmt.Sprintf("BlogTermRelationship ObjectID is missing: %+v", objIds))

			l.svcCtx.BlogTerm.DiagnoseTerm(l.ctx, objIds)
		}

		return len(objIds), nil
	}

	return
}

func (l *DiagnoseLogic) diagnoseEntryByTerm(in *blog.DiagnoseReq) (count int, err error) { /////
	filterMap := make(map[string]map[string][]any)
	filterMap[model.BlogTermRelationshipColumns.TermID] = map[string][]any{
		"equalTo": []any{14},
	}
	filterMap[model.BlogTermRelationshipColumns.ObjectType] = map[string][]any{
		"equalTo": []any{enumx.PostTypeKnowledgeBase},
	}
	relationList, _, err := l.svcCtx.BlogTerm.GetRelationshipList(l.ctx, model.M{
		"orderBy": model.BlogTermRelationshipColumns.ObjectID,
		"select":  model.BlogTermRelationshipColumns.ObjectID,
	}, -1, filterMap)
	if err != nil {
		return 0, err
	}

	if len(relationList) > 0 {
		var oids []any                      // []model.BlogTermRelationship.ObjectID(已去重)
		oidMap := make(map[uint64]struct{}) // Map: model.BlogTermRelationship.ObjectID => bool
		for _, relationship := range relationList {
			if relationship.ObjectID == 0 {
				continue
			}

			if _, ok := oidMap[relationship.ObjectID]; !ok {
				oids = append(oids, relationship.ObjectID)
				oidMap[relationship.ObjectID] = struct{}{}
			}
		}

		if len(oids) > 0 {
			subFilterMap := make(map[string]map[string][]any)
			subFilterMap[model.BlogKnowledgeBasisColumns.ID] = map[string][]any{
				"in": oids,
			}
			subFilterMap[model.BlogKnowledgeBasisColumns.PostStatus] = map[string][]any{
				"equalTo": []any{enumx.PostStatusPublish},
			}
			list, listCount, err := l.svcCtx.BlogBook.GetList(l.ctx, model.M{
				"orderBy": model.BlogKnowledgeBasisColumns.PostName,
				"select":  model.BlogKnowledgeBasisColumns.PostTitle + "," + model.BlogKnowledgeBasisColumns.PostName,
			}, 1, subFilterMap)
			if err != nil {
				return 0, err
			}

			count = int(listCount)
			titles := make([]string, 0, count)
			slugs := make([]string, 0, count)
			for _, brand := range list {
				titles = append(titles, brand.PostTitle)
				slugs = append(slugs, brand.PostName)
			}

			var titleId uint64 = 12153
			var slugId uint64 = 12154
			l.svcCtx.BlogBook.UpdateMetum(l.ctx, titleId, model.M{
				model.BlogKnowledgeBaseMetumColumns.MetaValue: strings.Join(titles, ","),
			})
			l.svcCtx.BlogBook.UpdateMetum(l.ctx, slugId, model.M{
				model.BlogKnowledgeBaseMetumColumns.MetaValue: strings.Join(slugs, ","),
			})
		}
	}

	return
}

func (l *DiagnoseLogic) diagnoseMetumByTerm(in *blog.DiagnoseReq) (count int, err error) { /////
	filterMap := make(map[string]map[string][]any)
	filterMap[model.BlogTermRelationshipColumns.TermID] = map[string][]any{
		"equalTo": []any{14},
	}
	filterMap[model.BlogTermRelationshipColumns.ObjectType] = map[string][]any{
		"equalTo": []any{enumx.PostTypeKnowledgeBase},
	}
	relationList, _, err := l.svcCtx.BlogTerm.GetRelationshipList(l.ctx, model.M{
		"page":     in.Current,
		"pageSize": in.PageSize,
		"orderBy":  model.BlogTermRelationshipColumns.ObjectID,
		"select":   model.BlogTermRelationshipColumns.ObjectID,
	}, -1, filterMap)
	if err != nil {
		return 0, err
	}

	if len(relationList) > 0 {
		var oids []uint64 // []model.BlogTermRelationship.ObjectID(已去重)
		var bids []any
		oidMap := make(map[uint64]struct{}) // Map: model.BlogTermRelationship.ObjectID => bool
		for _, relationship := range relationList {
			if relationship.ObjectID == 0 {
				continue
			}

			if _, ok := oidMap[relationship.ObjectID]; !ok {
				oids = append(oids, relationship.ObjectID)
				bids = append(bids, relationship.ObjectID)
				oidMap[relationship.ObjectID] = struct{}{}
			}
		}

		subFilterMap := make(map[string]map[string][]any)
		subFilterMap[model.BlogKnowledgeBaseMetumColumns.KnowledgeBaseID] = map[string][]any{
			"in": bids,
		}
		subFilterMap[model.BlogKnowledgeBaseMetumColumns.MetaKey] = map[string][]any{
			"equalTo": []any{"_perfume_count"},
		}
		list, _, err := l.svcCtx.BlogBook.GetMeta(l.ctx, model.M{
			"orderBy": model.BlogKnowledgeBaseMetumColumns.KnowledgeBaseID,
		}, -1, subFilterMap)
		if err != nil {
			return 0, err
		}

		midMap := make(map[uint64]uint64)
		if len(list) > 0 {
			for _, metum := range list {
				midMap[metum.KnowledgeBaseID] = metum.MetaID
			}
		}

		for _, oid := range oids {
			var mid uint64
			if id, ok := midMap[oid]; ok {
				mid = id
			}
			err = l.diagnoseBrandMetumById(oid, mid)
			if err != nil {
				return 0, err
			}

			count += 1
		}
	}

	return
}

func (l *DiagnoseLogic) diagnoseBrandMetumById(brandId uint64, metumId uint64) (err error) { /////
	var buf bytes.Buffer
	var query map[string]any
	var mustConditions []map[string]any
	mustConditions = append(mustConditions, map[string]any{
		"term": map[string]any{
			"Sections.Slug": "perfumes",
		},
	})
	mustConditions = append(mustConditions, map[string]any{
		"term": map[string]any{
			"Brand.Id": brandId,
		},
	})

	query = map[string]any{
		"query": map[string]any{
			"bool": map[string]any{
				"must": mustConditions,
			},
		},
		"_source": []string{"Id", "Title", "Slug"},
		"size":    int(500),
	}
	if err := json.NewEncoder(&buf).Encode(query); err != nil {
		logrus.Info(fmt.Sprintf("diagnoseBrandMetumById Error encoding query: %+v", err))
		return err
	}

	// Perform the search request.
	client := l.svcCtx.EsClient
	res, err := client.Search(
		client.Search.WithContext(l.ctx),
		client.Search.WithIndex("scentrobe-kb-entry"),
		client.Search.WithBody(&buf),
		client.Search.WithTrackTotalHits(true),
		client.Search.WithPretty(),
	)
	if err != nil {
		logrus.Info(fmt.Sprintf("diagnoseBrandMetumById Error getting response: %+v", err))
		return err
	}

	defer res.Body.Close()
	if res.IsError() {
		var e map[string]any
		if err = json.NewDecoder(res.Body).Decode(&e); err != nil {
			logrus.Info(fmt.Sprintf("diagnoseBrandMetumById Error parsing the response body: %+v", err))
		} else {
			// Print the response status and error information.
			logrus.Info(fmt.Sprintf(
				"[%s] %s: %s",
				res.Status(),
				e["error"].(map[string]any)["type"],
				e["error"].(map[string]any)["reason"],
			))
		}

		return err
	}

	// logrus.Info(fmt.Sprintf("DownloadAsExcel res: %+v", res))

	var r map[string]any
	if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
		logrus.Info(fmt.Sprintf("diagnoseBrandMetumById Error parsing the response body: %+v", err))
		return err
	}

	var total int64
	total = int64(r["hits"].(map[string]any)["total"].(map[string]any)["value"].(float64))

	if metumId > 0 {
		_, err = l.svcCtx.BlogBook.UpdateMetum(l.ctx, metumId, model.M{
			model.BlogKnowledgeBaseMetumColumns.MetaValue: total,
		})
	} else {
		var meta []*model.BlogKnowledgeBaseMetum
		meta = append(meta, &model.BlogKnowledgeBaseMetum{
			KnowledgeBaseID: brandId,
			MetaKey:         "_perfume_count",
			MetaValue:       null.StringFrom(strconv.FormatInt(total, 10)),
		})
		err = l.svcCtx.BlogBook.InsertMeta(l.ctx, meta)
	}
	if err != nil {
		logrus.Info(fmt.Sprintf("diagnoseBrandMetumById err: %+v", err))
		return err
	}

	return nil
}

func (l *DiagnoseLogic) diagnoseMetumByAggs(in *blog.DiagnoseReq) (count int, err error) { /////
	var page int64 = in.Current
	var pageSize int64 = in.PageSize
	terms := map[string]any{
		"field": "Brand.Link",
		"size":  page * pageSize,
	}
	aggs := map[string]any{
		"group_by_brand": map[string]any{
			"terms": terms,
			"aggs": map[string]any{
				"page": map[string]any{
					"bucket_sort": map[string]any{
						"sort": map[string]any{},
						"from": (page - 1) * pageSize,
						"size": pageSize,
					},
				},
			},
		},
	}
	query := map[string]any{
		"size": 0,
		"aggs": aggs,
	}

	// logrus.Info(fmt.Sprintf("PerfumeFilter query: %+v", query))

	var buf bytes.Buffer
	if err = json.NewEncoder(&buf).Encode(query); err != nil {
		logrus.Info(fmt.Sprintf("diagnoseMetumByAggs Error encoding query: %+v", err))
		return 0, err
	}

	// Perform the search request.
	client := l.svcCtx.EsClient
	res, err := client.Search(
		client.Search.WithContext(l.ctx),
		client.Search.WithIndex("scentrobe-kb-entry"),
		client.Search.WithBody(&buf),
		client.Search.WithTrackTotalHits(true),
		client.Search.WithPretty(),
	)
	if err != nil {
		logrus.Info(fmt.Sprintf("diagnoseMetumByAggs Error getting response: %+v", err))
		return 0, err
	}

	defer res.Body.Close()
	if res.IsError() {
		var e map[string]any
		if err = json.NewDecoder(res.Body).Decode(&e); err != nil {
			logrus.Info(fmt.Sprintf("diagnoseMetumByAggs Error parsing the response body: %+v", err))
		} else {
			// Print the response status and error information.
			logrus.Info(fmt.Sprintf(
				"[%s] %s: %s",
				res.Status(),
				e["error"].(map[string]any)["type"],
				e["error"].(map[string]any)["reason"],
			))
		}

		return 0, err
	}

	// logrus.Info(fmt.Sprintf("PerfumeFilter res: %+v", res))

	var r map[string]any
	if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
		logrus.Info(fmt.Sprintf("diagnoseMetumByAggs Error parsing the response body: %+v", err))
		return 0, err
	}

	// logrus.Info(fmt.Sprintf("diagnoseMetumByAggs aggregations: %+v", r))

	if _, ok := r["aggregations"]; ok {
		var bids []any
		bidMap := make(map[int64]string)
		for _, aggregation := range r["aggregations"].(map[string]any) {
			item := aggregation.(map[string]any)
			if _, ok := item["buckets"]; ok {
				for _, bucket := range item["buckets"].([]any) {
					parts := strings.Split(bucket.(map[string]any)["key"].(string), "~~~")
					if len(parts) == 3 {
						if i, err := strconv.ParseInt(parts[2], 10, 64); err == nil {
							bids = append(bids, i)
							bidMap[i] = strconv.FormatFloat(bucket.(map[string]any)["doc_count"].(float64), 'f', 0, 64)
						}
					}
				}
			}
		}

		subFilterMap := make(map[string]map[string][]any)
		subFilterMap[model.BlogKnowledgeBaseMetumColumns.KnowledgeBaseID] = map[string][]any{
			"in": bids,
		}
		subFilterMap[model.BlogKnowledgeBaseMetumColumns.MetaKey] = map[string][]any{
			"equalTo": []any{"_perfume_count"},
		}
		list, _, err := l.svcCtx.BlogBook.GetMeta(l.ctx, model.M{
			"orderBy": model.BlogKnowledgeBaseMetumColumns.KnowledgeBaseID,
		}, -1, subFilterMap)
		if err != nil {
			return 0, err
		}

		midMap := make(map[int64]uint64)
		if len(list) > 0 {
			for _, metum := range list {
				midMap[int64(metum.KnowledgeBaseID)] = metum.MetaID
			}
		}

		for bid, val := range bidMap {
			var mid uint64
			if id, ok := midMap[bid]; ok {
				mid = id
			}
			if mid > 0 {
				_, err = l.svcCtx.BlogBook.UpdateMetum(l.ctx, mid, model.M{
					model.BlogKnowledgeBaseMetumColumns.MetaValue: val,
				})
			} else {
				var meta []*model.BlogKnowledgeBaseMetum
				meta = append(meta, &model.BlogKnowledgeBaseMetum{
					KnowledgeBaseID: uint64(bid),
					MetaKey:         "_perfume_count",
					MetaValue:       null.StringFrom(val),
				})
				err = l.svcCtx.BlogBook.InsertMeta(l.ctx, meta)
			}
			if err != nil {
				logrus.Info(fmt.Sprintf("diagnoseMetumByAggs err: %+v", err))
				return 0, err
			}
		}

		return len(bids), nil
	}

	return
}

func (l *DiagnoseLogic) diagnoseEntryToEs(in *blog.DiagnoseReq) (count int, err error) { // ##25.06.27##
	blogBookRelationEntityName := model.RpcEntityNames.BlogBookRelation
	blogBookRelationSettingMap, ok := model.RpcEntityPreloadMap[blogBookRelationEntityName]
	if !ok {
		return 0, nil
	}

	var sectionIds []any
	sectionMap := make(map[uint64]string) // 记录 BlogBookRelation 的全部 section name
	for _, s := range blogBookRelationSettingMap {
		if s.Id > 0 {
			if _, ok := sectionMap[s.Id]; !ok {
				sectionMap[s.Id] = s.Name
				sectionIds = append(sectionIds, s.Id)
			}
		}
	}

	// var lastId uint64 = uint64(in.Current)
	// start:
	var wg sync.WaitGroup
	filterMap := make(map[string]map[string][]any)
	// filterMap[model.BlogKnowledgeBasisColumns.ID] = map[string][]any{
	// 	"moreThan": []any{lastId},
	// }
	filterMap[model.BlogKnowledgeBasisColumns.PostStatus] = map[string][]any{
		"equalTo": []any{enumx.PostStatusPublish},
	}
	entryList, _, listErr := l.svcCtx.BlogBook.GetList(l.ctx, model.M{
		"page":     in.Current,
		"pageSize": in.PageSize,
		"orderBy":  model.BlogKnowledgeBasisColumns.ID,
	}, -1, filterMap)
	if listErr != nil {
		return 0, listErr
	}

	if len(entryList) == 0 {
		return
	}

	var ids []any // []model.BlogKnowledgeBasis.ID
	for _, entry := range entryList {
		if entry.PostParentID > 0 { // 忽略香水版本
			continue
		}

		ids = append(ids, entry.ID)
	}

	idContentMap := make(map[uint64]string) // Map: model.BlogKnowledgeBasis.ID => string
	contentFilterMap := make(map[string]map[string][]any)
	contentFilterMap[model.BlogKnowledgeBaseDetailColumns.KnowledgeBaseID] = map[string][]any{
		"in": ids,
	}
	contentList, _, listErr := l.svcCtx.BlogBook.GetDetail(l.ctx, model.M{
		"select": model.BlogKnowledgeBaseDetailColumns.PostContent,
	}, -1, contentFilterMap)
	if listErr != nil {
		return 0, listErr
	}

	if len(contentList) > 0 {
		for _, content := range contentList {
			doc, docErr := modelx.RemoveHtmlTag(content.PostContent)
			if docErr != nil {
				logrus.Info(fmt.Sprintf("diagnoseEntryToEs RemoveHtmlTag ERROR: %+v, %d", docErr, content.KnowledgeBaseID))
				logx.Errorw("diagnoseEntryToEs RemoveHtmlTag err", logx.Field("detail", err.Error()), logx.Field("id", strconv.FormatInt(int64(content.KnowledgeBaseID), 10)))
			} else {
				idContentMap[content.KnowledgeBaseID] = doc
			}
		}
	}

	// 查询分类关联表
	subFilterMap := make(map[string]map[string][]any)
	subFilterMap[model.BlogTermRelationshipColumns.ObjectID] = map[string][]any{
		"in": ids,
	}
	subFilterMap[model.BlogTermRelationshipColumns.ObjectType] = map[string][]any{
		"equalTo": []any{enumx.PostTypeKnowledgeBase},
	}
	subFilterMap[model.BlogTermRelationshipColumns.TermID] = map[string][]any{
		"in": sectionIds, // todo: 这个限制会导致无法获取分类和标签，要么去除冗余代码，要么改写
	}
	relList, _, listErr := l.svcCtx.BlogTerm.GetRelationshipList(l.ctx, model.M{}, -1, subFilterMap)
	if listErr != nil {
		return 0, listErr
	}

	idCategoryMap := make(map[uint64][]*blog.BlogCategory) // Map: model.BlogKnowledgeBasis.ID => []*blog.BlogCategory
	idTagMap := make(map[uint64][]*blog.BlogTag)           // Map: model.BlogKnowledgeBasis.ID => []*blog.BlogTag
	idSectionMap := make(map[uint64][]*blog.BlogCategory)  // Map: model.BlogKnowledgeBasis.ID => []*blog.BlogCategory
	if len(relList) > 0 {
		tidMap := make(map[uint64][]uint64) // Map: model.BlogTerm.TermID => []model.BlogKnowledgeBasis.ID
		var tids []any                      // []model.BlogTerm.TermID(去重)
		for _, rel := range relList {
			if _, ok := tidMap[rel.TermID]; !ok {
				tids = append(tids, rel.TermID)
			}
			tidMap[rel.TermID] = append(tidMap[rel.TermID], rel.ObjectID)
		}

		termFilterMap := make(map[string]map[string][]any)
		termFilterMap[model.BlogTermColumns.TermID] = map[string][]any{
			"in": tids,
		}
		termList, _, listErr := l.svcCtx.BlogTerm.GetList(l.ctx, model.M{}, -1, termFilterMap)
		if listErr != nil {
			return 0, listErr
		}

		if len(termList) > 0 {
			for _, term := range termList {
				if oids, ok := tidMap[term.TermID]; ok {
					switch enumx.TermTaxonomy(term.Taxonomy) {
					case enumx.TermTaxonomyCategory:
						oidMap := make(map[uint64]struct{})
						for _, oid := range oids {
							if _, ok := oidMap[oid]; !ok {
								idCategoryMap[oid] = append(idCategoryMap[oid], &blog.BlogCategory{
									Id:       int64(term.TermID),
									Name:     term.Name,
									Slug:     term.Slug,
									ParentId: int64(term.ParentID),
								})
								oidMap[oid] = struct{}{}
							}
						}
					case enumx.TermTaxonomyTag:
						oidMap := make(map[uint64]struct{})
						for _, oid := range oids {
							if _, ok := oidMap[oid]; !ok {
								idTagMap[oid] = append(idTagMap[oid], &blog.BlogTag{
									Id:   int64(term.TermID),
									Name: term.Name,
									Slug: term.Slug,
								})
								oidMap[oid] = struct{}{}
							}
						}
					case enumx.TermTaxonomyKnowledgeBase:
						var alias string
						if s, ok := sectionMap[term.TermID]; ok {
							alias = s
						}
						oidMap := make(map[uint64]struct{})
						for _, oid := range oids {
							if _, ok := oidMap[oid]; !ok {
								idSectionMap[oid] = append(idSectionMap[oid], &blog.BlogCategory{
									Id:   int64(term.TermID),
									Name: term.Name,
									Slug: term.Slug,
									Uri:  alias,
								})
								oidMap[oid] = struct{}{}
							}
						}
					}
				}
			}
		}
	}

	idMediaMap := make(map[uint64][]*blog.BlogMedia) // Map: model.BlogKnowledgeBasis.ID => *blog.BlogMedia
	mediaFilterMap := make(map[string]map[string][]any)
	mediaFilterMap[model.BlogAttachmentRelationshipColumns.ObjectID] = map[string][]any{
		"in": ids,
	}
	mediaFilterMap[model.BlogAttachmentRelationshipColumns.ObjectType] = map[string][]any{
		"equalTo": []any{enumx.PostTypeKnowledgeBase},
	}
	mediaFilterMap[model.BlogAttachmentRelationshipColumns.AttachmentThumbnail] = map[string][]any{
		"equalTo": []any{1},
	}
	mediaList, _, listErr := l.svcCtx.BlogBook.GetMedia(l.ctx, model.M{
		"orderBy": model.BlogAttachmentRelationshipColumns.AttachmentOrder,
	}, -1, mediaFilterMap)
	if listErr != nil {
		return 0, listErr
	}

	if len(mediaList) > 0 {
		for _, media := range mediaList {
			if _, ok := idMediaMap[media.ObjectID]; !ok {
				idMediaMap[media.ObjectID] = append(idMediaMap[media.ObjectID], &blog.BlogMedia{
					Id:  int64(media.AttachmentID),
					Url: media.AttachmentImageURL,
				})
			}
		}
	}

	idMetaMap := make(map[uint64][]*blog.BlogMeta) // Map: model.BlogKnowledgeBasis.ID => []*blog.BlogMeta
	metaFilterMap := make(map[string]map[string][]any)
	metaFilterMap[model.BlogKnowledgeBaseMetumColumns.KnowledgeBaseID] = map[string][]any{
		"in": ids,
	}
	metaFilterMap[model.BlogKnowledgeBaseMetumColumns.MetaKey] = map[string][]any{
		"in": {"_year", "_max_product_gems", "_min_product_gems", "_average_rating", "_popularity", "_season", "_gender"},
	}
	metaList, _, listErr := l.svcCtx.BlogBook.GetMeta(l.ctx, model.M{}, -1, metaFilterMap)
	if listErr != nil {
		return 0, listErr
	}

	if len(metaList) > 0 {
		for _, metum := range metaList {
			if metum.MetaKey != "" && metum.MetaValue.Valid {
				idMetaMap[metum.KnowledgeBaseID] = append(idMetaMap[metum.KnowledgeBaseID], &blog.BlogMeta{
					Key:   metum.MetaKey,
					Value: metum.MetaValue.String,
				})
			}
		}
	}

	idEntriesMap := make(map[uint64][]*blog.BlogEntry) // Map: model.BlogKnowledgeBasis.ID => []*blog.BlogEntry
	relationFilterMap := make(map[string]map[string][]any)
	relationFilterMap[model.BlogKnowledgeBaseRelationshipColumns.FromEntryID] = map[string][]any{
		"in": ids,
	}
	relationFilterMap[model.BlogKnowledgeBaseRelationshipColumns.ToSectionID] = map[string][]any{
		"in": sectionIds,
	}
	relationSelectCols := []string{
		model.BlogKnowledgeBaseRelationshipColumns.FromEntryID,
		model.BlogKnowledgeBaseRelationshipColumns.ToEntryID,
		model.BlogKnowledgeBaseRelationshipColumns.ToSectionID,
		model.BlogKnowledgeBaseRelationshipColumns.ToEntryGroup,
	}
	entryRelList, _, listErr := l.svcCtx.BlogBook.GetRelationshipList(l.ctx, model.M{
		"select":  strings.Join(relationSelectCols, ","),
		"orderBy": model.BlogKnowledgeBaseRelationshipColumns.ToEntryOrder,
	}, -1, relationFilterMap)
	if listErr != nil {
		return 0, listErr
	}

	if len(entryRelList) > 0 {
		idRelMap := make(map[uint64][]*model.BlogKnowledgeBaseRelationship)
		tidMap := make(map[uint64]struct{}) // tids 去重 Map: model.BlogKnowledgeBasis.ID => bool
		var tids []any
		for _, rel := range entryRelList {
			idRelMap[rel.FromEntryID] = append(idRelMap[rel.FromEntryID], rel)
			if _, ok := tidMap[rel.ToEntryID]; !ok {
				tidMap[rel.ToEntryID] = struct{}{}
				tids = append(tids, rel.ToEntryID)
			}
		}
		if len(tids) > 0 {
			entryFilterMap := make(map[string]map[string][]any)
			entryFilterMap[model.BlogKnowledgeBasisColumns.ID] = map[string][]any{
				"in": tids,
			}
			entryFilterMap[model.BlogKnowledgeBasisColumns.PostStatus] = map[string][]any{
				"equalTo": []any{globalkey.PostStatusPublish},
			}
			entrySelectCols := []string{
				model.BlogKnowledgeBasisColumns.ID,
				model.BlogKnowledgeBasisColumns.PostTitle,
				model.BlogKnowledgeBasisColumns.PostName,
			}
			relEntryList, _, listErr := l.svcCtx.BlogBook.GetList(l.ctx, model.M{
				"select": strings.Join(entrySelectCols, ","),
			}, -1, entryFilterMap)
			if listErr != nil {
				return 0, listErr
			}

			relEntryMap := make(map[uint64]*model.BlogKnowledgeBasis)
			for _, e := range relEntryList {
				relEntryMap[e.ID] = e
			}
			for id, rs := range idRelMap {
				entryMap := make(map[uint64]struct{}) // Map: model.BlogKnowledgeBasis.ID => bool
				for _, r := range rs {
					if _, ok := entryMap[r.ToEntryID]; ok {
						continue
					}

					entryMap[r.ToEntryID] = struct{}{}

					if e, ok := relEntryMap[r.ToEntryID]; ok {
						var section string
						if s, exist := sectionMap[r.ToSectionID]; exist {
							section = s
						}
						idEntriesMap[id] = append(idEntriesMap[id], &blog.BlogEntry{
							Id:      int64(e.ID),
							Title:   e.PostTitle,
							Slug:    e.PostName,
							Section: section,
							GroupId: int64(r.ToEntryGroup),
							Url:     strings.ToLower(e.PostName[:1]),                                                                // 首字母
							Link:    e.PostTitle + "~~~" + strings.ToLower(e.PostName) + "~~~" + strconv.FormatInt(int64(e.ID), 10), // todo 作用不明
						})
					}
				}
			}
		}
	}

	for _, entry := range entryList {
		var sections []*blog.BlogCategory
		if terms, ok := idSectionMap[entry.ID]; ok {
			sections = terms[:]
		} else {
			// 忽略内部词条
			continue
		}

		var content string
		if s, ok := idContentMap[entry.ID]; ok {
			content = s
		}
		var categories []*blog.BlogCategory
		if terms, ok := idCategoryMap[entry.ID]; ok {
			categories = terms[:]
		}
		var tags []*blog.BlogTag
		if terms, ok := idTagMap[entry.ID]; ok {
			tags = terms[:]
		}
		var es []*blog.BlogEntry
		var brands []*blog.BlogEntry
		var noses []*blog.BlogEntry
		var groups []*blog.BlogEntry
		var notes []*blog.BlogEntry
		if res, ok := idEntriesMap[entry.ID]; ok {
			for _, re := range res {
				switch re.Section {
				case "brand":
					brands = append(brands, re)
				case "nose":
					noses = append(noses, re)
				case "group":
					groups = append(groups, re)
				case "note":
					notes = append(notes, re)
				default:
					es = append(es, re)
				}
			}
		}
		var media []*blog.BlogMedia
		if ms, ok := idMediaMap[entry.ID]; ok {
			media = ms[:]
		}
		var ms []*blog.BlogMeta
		var seasons []*blog.BlogMeta
		var genders []*blog.BlogMeta
		var year string
		var priceMax string
		var priceMin string
		var rating string
		var popularity string
		if res, ok := idMetaMap[entry.ID]; ok {
			for _, meta := range res {
				switch meta.Key {
				case "_year":
					year = meta.Value
				case "_max_product_gems":
					priceMax = meta.Value
				case "_min_product_gems":
					priceMin = meta.Value
				case "_average_rating":
					rating = meta.Value
				case "_popularity":
					popularity = meta.Value
				case "_season":
					parts := strings.Split(meta.Value, ",")
					if len(parts) > 0 {
						for _, season := range parts {
							var key string
							var val string
							switch season {
							case "spring":
								key = "1"
								val = "春~~~" + season + "~~~1"
							case "summer":
								key = "2"
								val = "夏~~~" + season + "~~~2"
							case "autumn":
								key = "3"
								val = "秋~~~" + season + "~~~3"
							case "winter":
								key = "4"
								val = "冬~~~" + season + "~~~4"
							}
							seasons = append(seasons, &blog.BlogMeta{
								Key:   key,
								Value: val,
							})
						}
					}
				case "_gender":
					parts := strings.Split(meta.Value, ",")
					if len(parts) > 0 {
						for _, gender := range parts {
							var key string
							var val string
							switch gender {
							case "unisex":
								key = "1"
								val = "中性香~~~" + gender + "~~~1"
							case "female":
								key = "2"
								val = "女香~~~" + gender + "~~~2"
							case "male":
								key = "3"
								val = "男香~~~" + gender + "~~~3"
							}
							genders = append(genders, &blog.BlogMeta{
								Key:   key,
								Value: val,
							})
						}
					}
				default:
					ms = append(ms, meta)
				}
			}
		}

		count += 1
		wg.Add(1)
		ctx := context.Background()
		go func(entry *blog.BookDetailResp) {
			defer wg.Done()
			l.AddEsData(ctx, entry)
		}(&blog.BookDetailResp{
			Id:          int64(entry.ID),
			Title:       entry.PostTitle,
			Slug:        entry.PostName,
			Uri:         entry.PostName,
			ContentHtml: content,
			Categories:  categories,
			Tags:        tags,
			Sections:    sections,
			Media:       media,
			Meta:        ms,
			Relations:   es,
			Brand:       brands,
			Nose:        noses,
			Group:       groups,
			Note:        notes,
			Season:      seasons,
			Gender:      genders,
			Year:        year,
			PriceMax:    priceMax,
			PriceMin:    priceMin,
			Rating:      rating,
			Popularity:  popularity,
		})
		// lastId = entry.ID
	}
	wg.Wait()
	// goto start

	return
}

func (l *DiagnoseLogic) AddEsData(ctx context.Context, entry *blog.BookDetailResp) error {
	// Build the request body.
	// Marshal Elasticsearch document struct object to JSON string
	docStr, err := l.jsonStruct(entry)
	if err != nil {
		return err
	}

	id := strconv.FormatInt(entry.Id, 10)
	// Instantiate a request object
	req := esapi.IndexRequest{
		Index:      "scentrobe-kb-entry",
		DocumentID: id,
		Body:       strings.NewReader(docStr),
		Refresh:    "false",
	}

	// Return an API response object from request
	res, err := req.Do(ctx, l.svcCtx.EsClient)
	if err != nil {
		logrus.Info(fmt.Sprintf("IndexRequest getting response ERROR: %+v", err))
		return err
	}

	defer res.Body.Close()

	if res.IsError() {
		logrus.Info(fmt.Sprintf("[%s] ERROR indexing document ID=%s, %+v", res.Status(), id, res))
		return errorx.Status(errorx.ERROR_ELASTIC_DOCUMENT_FAILED, "")
	}

	// Deserialize the response into a map.
	var resMap map[string]any
	if err = json.NewDecoder(res.Body).Decode(&resMap); err != nil {
		logrus.Info(fmt.Sprintf("Error parsing the response body: %s", err))
		return err
	}

	// logrus.Info(fmt.Sprintf("\nIndexRequest() RESPONSE:"))
	// // Print the response status and indexed document version.
	// logrus.Info(fmt.Sprintf("Status: %v", res.Status()))
	// logrus.Info(fmt.Sprintf("Result: %v", resMap["result"]))
	// logrus.Info(fmt.Sprintf("Version: %d", int(resMap["_version"].(float64))))
	// logrus.Info(fmt.Sprintf("resMap: %+v", resMap))
	// logrus.Info(fmt.Sprintf("\n"))
	return nil
}

// A function for marshaling structs to JSON string
func (l *DiagnoseLogic) jsonStruct(entry *blog.BookDetailResp) (string, error) {
	// logrus.Info(fmt.Sprintf("jsonStruct docStruct: %+v", entry))

	// Marshal the struct to JSON and check for errors
	b, err := json.Marshal(entry)
	if err != nil {
		logrus.Info(fmt.Sprintf("AddEsData jsonStruct json.Marshal ERROR: %+v", err))
		return "", err
	}

	return string(b), nil
}

func (l *DiagnoseLogic) diagnoseRelationship(in *blog.DiagnoseReq) (count int, err error) {
	list, _, err := l.svcCtx.BlogBook.GetRelationshipList(l.ctx, model.M{
		"page":     in.Current,
		"pageSize": in.PageSize,
		"orderBy":  model.BlogKnowledgeBaseRelationshipColumns.EntryRelationshipID,
	}, -1, nil)
	if err != nil {
		return 0, err
	}

	if len(list) > 0 {
		var entryIds []any // []model.BlogKnowledgeBasis.ID
		idMap := make(map[uint64]uint64)
		var objIds []uint64 // []model.BlogKnowledgeBasis.ID
		var ids []uint64    // []model.BlogKnowledgeBaseRelationship.EntryRelationshipID
		for _, item := range list {
			if _, ok := idMap[item.FromEntryID]; !ok {
				idMap[item.FromEntryID] = item.EntryRelationshipID

				entryIds = append(entryIds, item.FromEntryID)
			}
			if _, ok := idMap[item.ToEntryID]; !ok {
				idMap[item.ToEntryID] = item.EntryRelationshipID

				entryIds = append(entryIds, item.ToEntryID)
			}
		}

		if len(entryIds) > 0 {
			filterMap := make(map[string]map[string][]any)
			filterMap[model.BlogKnowledgeBasisColumns.ID] = map[string][]any{
				"in": entryIds,
			}
			entryList, _, err := l.svcCtx.BlogBook.GetList(l.ctx, model.M{
				"select": ",",
			}, -1, filterMap)
			if err != nil {
				return 0, err
			}

			for _, entry := range entryList {
				if _, ok := idMap[entry.ID]; ok {
					delete(idMap, entry.ID)
				}
			}
		}

		for objId, id := range idMap {
			objIds = append(objIds, objId)
			ids = append(ids, id)
		}
		if len(objIds) > 0 {
			logrus.Info(fmt.Sprintf("BlogKnowledgeBaseRelationship EntryID is missing: %+v", objIds))

			l.svcCtx.BlogTerm.DiagnoseRelationship(l.ctx, ids)
		}

		return len(objIds), nil
	}

	return
}

func (l *DiagnoseLogic) diagnoseThumbnail(in *blog.DiagnoseReq) (count int, err error) { /////
	filterMap := make(map[string]map[string][]any)
	filterMap[model.BlogKnowledgeBaseMetumColumns.MetaKey] = map[string][]any{
		"in": []any{"_thumbnail_id", "_thumbnail_url"},
	}
	list, _, err := l.svcCtx.BlogBook.GetMeta(l.ctx, model.M{
		"page":     in.Current,
		"pageSize": in.PageSize,
		"orderBy":  model.BlogKnowledgeBaseMetumColumns.MetaID,
	}, -1, nil)
	if err != nil {
		return 0, err
	}

	if len(list) > 0 {
		idMap := make(map[uint64]uint64)
		thumbnailIdMap := make(map[uint64]uint64)
		thumbnailMetaIdMap := make(map[uint64]uint64)
		var entryIds []any // []model.BlogAttachmentRelationship.ObjectID
		for _, item := range list {
			if _, ok := idMap[item.MetaID]; !ok {
				idMap[item.MetaID] = item.KnowledgeBaseID

				key := strings.TrimSpace(item.MetaKey)
				if len(key) > 0 && item.MetaValue.Valid {
					val := strings.TrimSpace(item.MetaValue.String)

					if key == "_thumbnail_id" {
						if i, err := strconv.ParseInt(val, 10, 64); err == nil {
							thumbnailIdMap[item.KnowledgeBaseID] = uint64(i)
						}
					} else if key == "_thumbnail_url" {
						if strings.HasPrefix(val, "https://www.scentrobe.com/proxy") {
							entryIds = append(entryIds, item.KnowledgeBaseID)
							logrus.Info(fmt.Sprintf("BlogKnowledgeBaseMetum _thumbnail_url is deprecated: %+v", item))
							thumbnailMetaIdMap[item.KnowledgeBaseID] = item.MetaID
						}
					}
				}
			}
		}

		if len(entryIds) > 0 {
			filterMap := make(map[string]map[string][]any)
			filterMap[model.BlogAttachmentRelationshipColumns.ObjectID] = map[string][]any{
				"in": entryIds,
			}
			mediaList, _, err := l.svcCtx.BlogBook.GetMedia(l.ctx, model.M{}, -1, filterMap)
			if err != nil {
				return 0, err
			}

			for _, media := range mediaList {
				if thumbnailId, ok := thumbnailIdMap[media.ObjectID]; ok && thumbnailId == media.AttachmentID {
					if thumbnailMetaId, ok := thumbnailMetaIdMap[media.ObjectID]; ok {
						l.svcCtx.BlogBook.UpdateMetum(l.ctx, thumbnailMetaId, model.M{
							model.BlogKnowledgeBaseMetumColumns.MetaValue: media.AttachmentImageURL,
						})
					}
				}
			}
		}
	}

	return
}

func (l *DiagnoseLogic) diagnoseEntryAttachment(in *blog.DiagnoseReq) (count int, err error) { //////
	blogBookRelationEntityName := model.RpcEntityNames.BlogBookRelation
	blogBookRelationSettingMap, hasBlogBookRelationSettingMap := model.RpcEntityPreloadMap[blogBookRelationEntityName]
	if !hasBlogBookRelationSettingMap {
		return 0, nil
	}

	var sectionIds []any
	sectionMap := make(map[uint64]string) // 记录 BlogBookRelation 的全部 section name
	for _, s := range blogBookRelationSettingMap {
		if s.Id > 0 {
			if _, ok := sectionMap[s.Id]; !ok {
				sectionMap[s.Id] = s.Name
				sectionIds = append(sectionIds, s.Id)
			}
		}
	}

	filterMap := make(map[string]map[string][]any)
	filterMap[model.BlogKnowledgeBasisColumns.PostStatus] = map[string][]any{
		"equalTo": []any{globalkey.PostStatusPublish},
	}
	entryList, _, listErr := l.svcCtx.BlogBook.GetList(l.ctx, model.M{
		"page":     in.Current,
		"pageSize": in.PageSize,
		"orderBy":  model.BlogKnowledgeBasisColumns.ID,
	}, -1, filterMap)
	if listErr != nil {
		return 0, listErr
	}

	if len(entryList) == 0 {
		return
	}

	var ids []any // []model.BlogKnowledgeBasis.ID
	for _, entry := range entryList {
		if entry.PostParentID > 0 { // 忽略香水版本
			continue
		}

		ids = append(ids, entry.ID)
	}

	// 查询分类关联表
	subFilterMap := make(map[string]map[string][]any)
	subFilterMap[model.BlogTermRelationshipColumns.ObjectID] = map[string][]any{
		"in": ids,
	}
	subFilterMap[model.BlogTermRelationshipColumns.ObjectType] = map[string][]any{
		"equalTo": []any{enumx.PostTypeKnowledgeBase},
	}
	subFilterMap[model.BlogTermRelationshipColumns.TermID] = map[string][]any{
		"in": sectionIds,
	}
	relList, _, listErr := l.svcCtx.BlogTerm.GetRelationshipList(l.ctx, model.M{}, -1, subFilterMap)
	if listErr != nil {
		return 0, listErr
	}

	tidMap := make(map[uint64]uint64) // Map: model.BlogKnowledgeBasis.ID => model.BlogTerm.TermID
	if len(relList) > 0 {
		for _, rel := range relList {
			tidMap[rel.ObjectID] = rel.TermID
		}
	}

	midMap := make(map[uint64][]uint64) // Map: model.BlogKnowledgeBasis.ID => model.BlogAttachmentRelationship.AttachmentID
	mediaFilterMap := make(map[string]map[string][]any)
	mediaFilterMap[model.BlogAttachmentRelationshipColumns.ObjectID] = map[string][]any{
		"in": ids,
	}
	mediaFilterMap[model.BlogAttachmentRelationshipColumns.ObjectType] = map[string][]any{
		"equalTo": []any{enumx.PostTypeKnowledgeBase},
	}
	mediaList, _, listErr := l.svcCtx.BlogBook.GetMedia(l.ctx, model.M{
		"orderBy": model.BlogAttachmentRelationshipColumns.AttachmentOrder,
	}, -1, mediaFilterMap)
	if listErr != nil {
		return 0, listErr
	}

	if len(mediaList) > 0 {
		for _, media := range mediaList {
			midMap[media.ObjectID] = append(midMap[media.ObjectID], media.AttachmentID)
		}
	}

	var wg sync.WaitGroup
	for _, entry := range entryList {
		var sectionId uint64
		if sid, ok := tidMap[entry.ID]; ok {
			sectionId = sid
		} else {
			// 忽略内部词条
			continue
		}

		var newAttachmentIds []string
		if ids, ok := midMap[entry.ID]; ok {
			aidMap := make(map[uint64]struct{})
			for _, id := range ids {
				if _, ok := aidMap[id]; !ok {
					newAttachmentIds = append(newAttachmentIds, strconv.FormatInt(int64(id), 10))
					aidMap[id] = struct{}{}
				}
			}
		}

		count += 1
		wg.Add(1)
		ctx := context.Background()
		go func() {
			defer wg.Done()
			l.addAttachmentLog(ctx, entry, newAttachmentIds, sectionId)
		}()
	}
	wg.Wait()

	return
}

func (l *DiagnoseLogic) addAttachmentLog(
	ctx context.Context,
	entry *model.BlogKnowledgeBasis,
	attachmentIds []string,
	sectionId uint64,
) error {
	_, err := l.svcCtx.AttachmentRpc.BackendCreateLog(l.ctx, &attachmentclient.CreateLogReq{
		FileIds:    attachmentIds,
		UserId:     pointy.GetPointer(strconv.FormatInt(int64(entry.PostAuthorID), 10)),
		Put:        pointy.GetPointer(uint32(1)),
		Thumbnail:  pointy.GetPointer(uint32(1)),
		TermId:     pointy.GetPointer(sectionId),
		ObjectId:   pointy.GetPointer(entry.ID),
		ObjectType: pointy.GetPointer(uint32(globalkey.AttachmentLogKbEntry)),
		Title:      pointy.GetPointer(entry.PostTitle),
		Slug:       pointy.GetPointer(entry.PostName),
	})
	if err != nil {
		logx.Errorw("diagnoseEntryAttachment AddAttachmentLog err", logx.Field("detail", err.Error()))
		return err
	}

	return nil
}
